feat: map-based LLM provider config with pricing dialog - #3492
Conversation
|
Note Reviews pausedIt looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the Use the following commands to manage reviews:
Use the checkboxes below for quick actions:
📝 WalkthroughWalkthroughAdds provider-defaults support and token pricing fields across backend models, service merging logic, unit/e2e tests, generated API schemas, and frontend UI components to display pricing when billing is enabled. Changes
Sequence Diagram(s)sequenceDiagram
participant Config as LlmProperties (providers + providerDefaults)
participant Service as LlmPropertiesService
participant Assembler as LlmProviderSimpleModelAssembler
participant UI as Frontend (Providers View)
participant Billing as Billing API
Config->>Service: getMergedProviders()
Service->>Service: mergeProviderWithDefaults() per provider entry
Service-->>Assembler: merged LlmProvider(s) (incl. tokenPrice fields)
Assembler-->>UI: serve LlmProviderSimpleModel
UI->>Billing: fetch subscription (billingEnabled, credits)
Billing-->>UI: perThousandMtCredits
UI->>UI: render TokenPricesTable / EstimateCostTable / PricingDialog when billingEnabled and pricing present
Estimated code review effort🎯 4 (Complex) | ⏱️ ~45 minutes Possibly related PRs
Suggested labels
Suggested reviewers
Poem
🚥 Pre-merge checks | ✅ 2 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (2 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches🧪 Generate unit tests (beta)
Comment |
There was a problem hiding this comment.
Actionable comments posted: 5
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (1)
webapp/CLAUDE.md (1)
80-105:⚠️ Potential issue | 🟠 MajorAdd explicit
EXPECT_NO_CONFLICTresolution to the import example to prevent accidental key overwrites.The
single-step-import-resolvableendpoint example (lines 80–105) should include the conflict resolution strategy. Without it, the API may behave unpredictably if a key already exists in the project.Suggested doc update
curl -X POST "https://app.tolgee.io/v2/projects/single-step-import-resolvable" \ -H "X-API-Key: ${VITE_APP_TOLGEE_API_KEY}" \ -H "Content-Type: application/json" \ -d '{ + "resolution": "EXPECT_NO_CONFLICT", "keys": [ { "name": "my_key",🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@webapp/CLAUDE.md` around lines 80 - 105, Update the single-step-import-resolvable example to include an explicit conflict resolution field by adding "resolution": "EXPECT_NO_CONFLICT" at the top-level of the POST body for the POST https://app.tolgee.io/v2/projects/single-step-import-resolvable example; modify the JSON payload shown in the example so that the request always specifies resolution: "EXPECT_NO_CONFLICT" (so key creation via the "keys" array will fail when a conflicting key already exists), keeping the rest of the payload structure (keys, translations, tags, screenshots) unchanged.
🧹 Nitpick comments (2)
backend/data/src/main/kotlin/io/tolgee/service/LlmPropertiesService.kt (1)
29-31: Nit: prefer.any {}over.find {} != null.Proposed fix
- val hasTolgeeConfig = result.find { it.type == LlmProviderType.TOLGEE } != null + val hasTolgeeConfig = result.any { it.type == LlmProviderType.TOLGEE }🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@backend/data/src/main/kotlin/io/tolgee/service/LlmPropertiesService.kt` around lines 29 - 31, Replace the usage of result.find { it.type == LlmProviderType.TOLGEE } != null with a more idiomatic check using result.any { it.type == LlmProviderType.TOLGEE } inside LlmPropertiesService where getMergedProviders() is assigned to result and subscriptionActive() is checked; update the hasTolgee assignment accordingly to use .any for readability and performance.webapp/src/ee/llm/OrganizationLLMProviders/LlmProviderPricingDialog.tsx (1)
198-301: Consider extracting the repeated estimate row pattern to reduce duplication.The three estimate table rows (lines 199–232, 233–266, 267–300) share identical structure, differing only in the
data-cyvalue, label key, and input token constant. This could be extracted into a small helper component or mapped over an array.Example refactor
const ESTIMATES = [ { cy: 'llm-provider-pricing-estimate-context-and-screenshots', labelKey: 'llm_provider_pricing_context_and_screenshots', labelDefault: 'Context + screenshots', inputTokens: INPUT_TOKENS_CONTEXT_AND_SCREENSHOTS, }, { cy: 'llm-provider-pricing-estimate-context-no-screenshots', labelKey: 'llm_provider_pricing_context_no_screenshots', labelDefault: 'Context, no screenshots', inputTokens: INPUT_TOKENS_CONTEXT_NO_SCREENSHOTS, }, { cy: 'llm-provider-pricing-estimate-no-context', labelKey: 'llm_provider_pricing_no_context', labelDefault: 'No context', inputTokens: INPUT_TOKENS_NO_CONTEXT, }, ] as const; // Then in the TableBody: {ESTIMATES.map(({ cy, labelKey, labelDefault, inputTokens }) => ( <TableRow key={cy} data-cy={cy}> <TableCell><T keyName={labelKey} defaultValue={labelDefault} /></TableCell> <TableCell align="right">{'~'}{formatCredits(inputTokens + OUTPUT_TOKENS)}</TableCell> <TableCell align="right">{'~'}{formatCredits(estimateCredits(inputTokens, OUTPUT_TOKENS)!)}</TableCell> {pricePerMtCredit != null && ( <TableCell align="right">{'~'}{creditsToEur(estimateCredits(inputTokens, OUTPUT_TOKENS)!)}</TableCell> )} </TableRow> ))}🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@webapp/src/ee/llm/OrganizationLLMProviders/LlmProviderPricingDialog.tsx` around lines 198 - 301, The three nearly identical rows (TableRow with data-cy values llm-provider-pricing-estimate-context-and-screenshots, llm-provider-pricing-estimate-context-no-screenshots, llm-provider-pricing-estimate-no-context) duplicate markup; extract them into a small reusable mapping or helper component (e.g., an ESTIMATES array and map over it) that supplies data-cy, label key/default, and inputTokens (INPUT_TOKENS_CONTEXT_AND_SCREENSHOTS, INPUT_TOKENS_CONTEXT_NO_SCREENSHOTS, INPUT_TOKENS_NO_CONTEXT) and then render the shared cells using formatCredits, estimateCredits, creditsToEur and the pricePerMtCredit conditional to remove repetition while preserving behavior.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In `@e2e/cypress/e2e/llmProviders/llmProviders.cy.ts`:
- Around line 103-107: Replace the text-based tab click in the test by targeting
the Server tab via its data-cy selector instead of using
gcy('organization-llm-providers-tab').contains('Server').click(); specifically
change the interaction to use the dedicated selector (e.g.,
gcy('organization-llm-providers-server-tab').click()) so the test uses the
data-cy-driven tab switch and then keep the assertions on
gcy('llm-provider-item-name') and gcy('llm-provider-pricing-info') as-is.
In `@webapp/CLAUDE.md`:
- Line 16: Change the absolute instruction "**Important:** Remember to upload
the context (BigMeta) and Screenshots for each key." to a conditional note that
BigMeta uploads are required only for multi-key changes and that BigMeta
requires a minimum of two related keys; update the related guidance around the
"BigMeta requires at least 2 keys" wording (the block referencing BigMeta and
Screenshots) so it states "Upload context (BigMeta) for related keys with a
minimum of 2 related keys required" and adjust the examples/lines that currently
say "always upload" (the BigMeta/Screenshots guidance) to make clear single-key
changes do not require a BigMeta upload.
In `@webapp/src/ee/llm/OrganizationLLMProviders/LlmProviderPricingDialog.tsx`:
- Around line 206-211: The Tokens cell currently shows per-string tokens (using
INPUT_TOKENS_CONTEXT_AND_SCREENSHOTS + OUTPUT_TOKENS) while the header is
"Estimated cost for 1,000 strings"; update the Tokens column to show the total
for STRINGS_COUNT by multiplying the token expression by STRINGS_COUNT (e.g.,
(INPUT_TOKENS_CONTEXT_AND_SCREENSHOTS + OUTPUT_TOKENS) * STRINGS_COUNT) and pass
that value to the same formatting utility you use for credits (or create/ reuse
a formatTokens helper) so Tokens and Credits are both presented on the same
1,000-strings basis; alternatively, if you prefer per-string display, change the
column header to include "per string" instead of modifying the value.
- Around line 16-19: The import statement for useMoneyFormatter and
useNumberFormatter is misformatted for Prettier; replace the grouped multi-line
import with a single-line import: import { useMoneyFormatter, useNumberFormatter
} from 'tg.hooks/useLocale'; so that the symbols useMoneyFormatter and
useNumberFormatter are imported together on one line and adhere to
Prettier/linters.
- Around line 144-153: Prettier flags formatting problems in
LlmProviderPricingDialog.tsx around the TableCell lines showing
formatCredits(inputPrice!) and formatCredits(outputPrice!); open the
LlmProviderPricingDialog.tsx file and run the project formatter (e.g., npm/yarn
prettierrc or the repository's format script) or apply Prettier formatting to
the block containing the TableRow/TableCell/T components so spacing and line
breaks match project style, then save/commit the changes; verify the formatted
symbols are TableCell, formatCredits, inputPrice, outputPrice and the T
component remain unchanged functionally.
---
Outside diff comments:
In `@webapp/CLAUDE.md`:
- Around line 80-105: Update the single-step-import-resolvable example to
include an explicit conflict resolution field by adding "resolution":
"EXPECT_NO_CONFLICT" at the top-level of the POST body for the POST
https://app.tolgee.io/v2/projects/single-step-import-resolvable example; modify
the JSON payload shown in the example so that the request always specifies
resolution: "EXPECT_NO_CONFLICT" (so key creation via the "keys" array will fail
when a conflicting key already exists), keeping the rest of the payload
structure (keys, translations, tags, screenshots) unchanged.
---
Nitpick comments:
In `@backend/data/src/main/kotlin/io/tolgee/service/LlmPropertiesService.kt`:
- Around line 29-31: Replace the usage of result.find { it.type ==
LlmProviderType.TOLGEE } != null with a more idiomatic check using result.any {
it.type == LlmProviderType.TOLGEE } inside LlmPropertiesService where
getMergedProviders() is assigned to result and subscriptionActive() is checked;
update the hasTolgee assignment accordingly to use .any for readability and
performance.
In `@webapp/src/ee/llm/OrganizationLLMProviders/LlmProviderPricingDialog.tsx`:
- Around line 198-301: The three nearly identical rows (TableRow with data-cy
values llm-provider-pricing-estimate-context-and-screenshots,
llm-provider-pricing-estimate-context-no-screenshots,
llm-provider-pricing-estimate-no-context) duplicate markup; extract them into a
small reusable mapping or helper component (e.g., an ESTIMATES array and map
over it) that supplies data-cy, label key/default, and inputTokens
(INPUT_TOKENS_CONTEXT_AND_SCREENSHOTS, INPUT_TOKENS_CONTEXT_NO_SCREENSHOTS,
INPUT_TOKENS_NO_CONTEXT) and then render the shared cells using formatCredits,
estimateCredits, creditsToEur and the pricePerMtCredit conditional to remove
repetition while preserving behavior.
ℹ️ Review info
Configuration used: Path: .coderabbit.yaml
Review profile: CHILL
Plan: Pro
⛔ Files ignored due to path filters (2)
e2e/package-lock.jsonis excluded by!**/package-lock.jsonlibrary/package-lock.jsonis excluded by!**/package-lock.json
📒 Files selected for processing (13)
backend/api/src/main/kotlin/io/tolgee/hateoas/llmProvider/LlmProviderSimpleModel.ktbackend/data/src/main/kotlin/io/tolgee/configuration/tolgee/machineTranslation/LlmProperties.ktbackend/data/src/main/kotlin/io/tolgee/service/LlmPropertiesService.ktbackend/data/src/test/kotlin/io/tolgee/unit/LlmPropertiesServiceMergeTest.kte2e/cypress/e2e/llmProviders/llmProviders.cy.tse2e/cypress/support/dataCyType.d.tsee/backend/app/src/main/kotlin/io/tolgee/ee/api/v2/hateoas/assemblers/LlmProviderSimpleModelAssembler.ktwebapp/CLAUDE.mdwebapp/src/ee/llm/OrganizationLLMProviders/LlmProviderPricingDialog.tsxwebapp/src/ee/llm/OrganizationLLMProviders/LlmProviderPricingInfo.tsxwebapp/src/ee/llm/OrganizationLLMProviders/LlmProvidersServer.tsxwebapp/src/service/apiSchema.generated.tswebapp/src/service/billingApiSchema.generated.ts
There was a problem hiding this comment.
♻️ Duplicate comments (1)
webapp/src/ee/llm/OrganizationLLMProviders/LlmProviderPricingDialog.tsx (1)
207-211: Tokens unit is still inconsistent with the “1,000 strings” estimate table.The Tokens column shows per-string token counts, while Credits/EUR are totals for 1,000 strings. Keep units aligned (multiply Tokens by
STRINGS_COUNTor relabel as per-string).Also applies to: 241-245, 275-277
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@webapp/src/ee/llm/OrganizationLLMProviders/LlmProviderPricingDialog.tsx` around lines 207 - 211, The Tokens column displays per-string token counts but Credits/EUR are totals for 1,000 strings; to align units, multiply the token total by STRINGS_COUNT where the component renders token totals (e.g., the expression INPUT_TOKENS_CONTEXT_AND_SCREENSHOTS + OUTPUT_TOKENS inside the TableCell and the similar expressions at the other occurrences) before passing to formatCredits or rendering, i.e., replace formatCredits(INPUT_TOKENS_CONTEXT_AND_SCREENSHOTS + OUTPUT_TOKENS) with formatCredits((INPUT_TOKENS_CONTEXT_AND_SCREENSHOTS + OUTPUT_TOKENS) * STRINGS_COUNT) so Tokens and Credits/EUR both represent totals for STRINGS_COUNT strings.
🧹 Nitpick comments (1)
webapp/src/ee/llm/OrganizationLLMProviders/LlmProviderPricingDialog.tsx (1)
213-294: Compute each scenario estimate once to reduce duplication.Each row recalculates
estimateCredits(...)for both Credits and EUR cells. Precomputing per-row estimates improves readability and reduces repeated non-null assertions.♻️ Refactor sketch
+ const estimateContextAndScreenshots = estimateCredits( + INPUT_TOKENS_CONTEXT_AND_SCREENSHOTS, + OUTPUT_TOKENS + ); + const estimateContextNoScreenshots = estimateCredits( + INPUT_TOKENS_CONTEXT_NO_SCREENSHOTS, + OUTPUT_TOKENS + ); + const estimateNoContext = estimateCredits( + INPUT_TOKENS_NO_CONTEXT, + OUTPUT_TOKENS + ); ... - {formatCredits( - estimateCredits( - INPUT_TOKENS_CONTEXT_AND_SCREENSHOTS, - OUTPUT_TOKENS - )! - )} + {formatCredits(estimateContextAndScreenshots!)} ... - {creditsToEur( - estimateCredits( - INPUT_TOKENS_CONTEXT_AND_SCREENSHOTS, - OUTPUT_TOKENS - )! - )} + {creditsToEur(estimateContextAndScreenshots!)}🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@webapp/src/ee/llm/OrganizationLLMProviders/LlmProviderPricingDialog.tsx` around lines 213 - 294, Rows repeatedly call estimateCredits(...) (with non-null assertions) for the Credits and EUR cells; compute each row's estimate once into a local const (e.g. const estimate = estimateCredits(INPUT_TOKENS_..., OUTPUT_TOKENS)) and use that variable in both formatCredits(estimate) and creditsToEur(estimate) (guard for null/undefined or use conditional rendering based on pricePerMtCredit), replacing repeated estimateCredits(...) calls and removing duplicated non-null assertions; apply this pattern for the rows using INPUT_TOKENS_CONTEXT_AND_SCREENSHOTS, INPUT_TOKENS_CONTEXT_NO_SCREENSHOTS, and INPUT_TOKENS_NO_CONTEXT and keep existing symbols like formatCredits, creditsToEur, and pricePerMtCredit.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Duplicate comments:
In `@webapp/src/ee/llm/OrganizationLLMProviders/LlmProviderPricingDialog.tsx`:
- Around line 207-211: The Tokens column displays per-string token counts but
Credits/EUR are totals for 1,000 strings; to align units, multiply the token
total by STRINGS_COUNT where the component renders token totals (e.g., the
expression INPUT_TOKENS_CONTEXT_AND_SCREENSHOTS + OUTPUT_TOKENS inside the
TableCell and the similar expressions at the other occurrences) before passing
to formatCredits or rendering, i.e., replace
formatCredits(INPUT_TOKENS_CONTEXT_AND_SCREENSHOTS + OUTPUT_TOKENS) with
formatCredits((INPUT_TOKENS_CONTEXT_AND_SCREENSHOTS + OUTPUT_TOKENS) *
STRINGS_COUNT) so Tokens and Credits/EUR both represent totals for STRINGS_COUNT
strings.
---
Nitpick comments:
In `@webapp/src/ee/llm/OrganizationLLMProviders/LlmProviderPricingDialog.tsx`:
- Around line 213-294: Rows repeatedly call estimateCredits(...) (with non-null
assertions) for the Credits and EUR cells; compute each row's estimate once into
a local const (e.g. const estimate = estimateCredits(INPUT_TOKENS_...,
OUTPUT_TOKENS)) and use that variable in both formatCredits(estimate) and
creditsToEur(estimate) (guard for null/undefined or use conditional rendering
based on pricePerMtCredit), replacing repeated estimateCredits(...) calls and
removing duplicated non-null assertions; apply this pattern for the rows using
INPUT_TOKENS_CONTEXT_AND_SCREENSHOTS, INPUT_TOKENS_CONTEXT_NO_SCREENSHOTS, and
INPUT_TOKENS_NO_CONTEXT and keep existing symbols like formatCredits,
creditsToEur, and pricePerMtCredit.
There was a problem hiding this comment.
🧹 Nitpick comments (1)
webapp/CLAUDE.md (1)
107-108: Clarify repeated keys guidance for better precision.The statement "A key appearing multiple times (e.g. repeated buttons) should have multiple entries in
positions" could be misinterpreted. It's unclear whether this means multiple position objects within thepositionsarray of a single key, or multiple key entries in thekeysarray.📝 Suggested clarification
-Map each entry from `getVisibleKeys()` to a key in the `keys` array, using the `position` values for `positions`. -A key appearing multiple times (e.g. repeated buttons) should have multiple entries in `positions`. +Map each entry from `getVisibleKeys()` to a key in the `keys` array, using the `position` values for `positions`. +If the same key appears multiple times on the page (e.g. repeated buttons), add multiple position objects to the `positions` array within a single `screenshots` entry.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@webapp/CLAUDE.md` around lines 107 - 108, The wording is ambiguous about repeated physical keys; update the guidance in CLAUDE.md so it explicitly states the mapping: for each entry returned by getVisibleKeys() create (or map to) a single key object in the keys array and collect all physical occurrences of that key as multiple position objects inside that key object's positions array (do not create duplicate key entries in keys). Mention the alternative only if the design requires distinct logical keys (i.e., create multiple keys in keys array), but default to the single-key-with-multiple-positions approach and show the exact relationship between getVisibleKeys(), keys, and positions.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Nitpick comments:
In `@webapp/CLAUDE.md`:
- Around line 107-108: The wording is ambiguous about repeated physical keys;
update the guidance in CLAUDE.md so it explicitly states the mapping: for each
entry returned by getVisibleKeys() create (or map to) a single key object in the
keys array and collect all physical occurrences of that key as multiple position
objects inside that key object's positions array (do not create duplicate key
entries in keys). Mention the alternative only if the design requires distinct
logical keys (i.e., create multiple keys in keys array), but default to the
single-key-with-multiple-positions approach and show the exact relationship
between getVisibleKeys(), keys, and positions.
ℹ️ Review info
Configuration used: Path: .coderabbit.yaml
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (7)
e2e/cypress/e2e/llmProviders/llmProviders.cy.tse2e/cypress/support/dataCyType.d.tswebapp/CLAUDE.mdwebapp/src/ee/llm/OrganizationLLMProviders/EstimateCostTable.tsxwebapp/src/ee/llm/OrganizationLLMProviders/LlmProviderPricingDialog.tsxwebapp/src/ee/llm/OrganizationLLMProviders/OrganizationLlmProvidersView.tsxwebapp/src/ee/llm/OrganizationLLMProviders/TokenPricesTable.tsx
🚧 Files skipped from review as they are similar to previous changes (1)
- e2e/cypress/support/dataCyType.d.ts
There was a problem hiding this comment.
Actionable comments posted: 1
🧹 Nitpick comments (1)
backend/data/src/main/kotlin/io/tolgee/configuration/tolgee/machineTranslation/LlmProperties.kt (1)
174-179: ReuseLlmProvider.TYPE_DEFAULTinLlmProviderDefaults.
mergeProviderWithDefaults()usesLlmProvider.TYPE_DEFAULTas the sentinel for “Spring supplied the default”. Hard-codingOPENAIagain here lets those two drift later and would silently change the merge behavior.Suggested change
- var type: LlmProviderType = LlmProviderType.OPENAI, + var type: LlmProviderType = LlmProvider.TYPE_DEFAULT,🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@backend/data/src/main/kotlin/io/tolgee/configuration/tolgee/machineTranslation/LlmProperties.kt` around lines 174 - 179, LlmProviderDefaults currently hard-codes the default provider type to LlmProviderType.OPENAI; change it to reuse the shared sentinel LlmProvider.TYPE_DEFAULT so it stays consistent with mergeProviderWithDefaults. Update the type property in class LlmProviderDefaults (and any constructor defaults there) to use LlmProvider.TYPE_DEFAULT instead of LlmProviderType.OPENAI, ensuring mergeProviderWithDefaults sees the same sentinel value.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In
`@backend/data/src/main/kotlin/io/tolgee/configuration/tolgee/machineTranslation/LlmProperties.kt`:
- Around line 71-78: Update the env-var examples in LlmProperties so they follow
Spring Boot's binding rules: use the canonical property
tolgee.llm.provider-defaults.gpt-5-mini.type and its env-var form
TOLGEE_LLM_PROVIDERDEFAULTSGPT5MINITYPE (no dashes/underscores between parts)
and note the map key will be lowercased (gpt5mini). Replace the incorrect
examples (e.g. TOLGEE_LLM_PROVIDER_DEFAULTS_GPT_5_MINI_TYPE) with the correct
concatenated uppercase names for type/model and token price keys (e.g.
TOLGEE_LLM_PROVIDERDEFAULTSGPT5MINITYPE,
TOLGEE_LLM_PROVIDERDEFAULTSGPT5MINIMODEL,
TOLGEE_LLM_PROVIDERDEFAULTSGPT5MINITOKENPRICEINCREDITSINPUT,
TOLGEE_LLM_PROVIDERDEFAULTSGPT5MINITOKENPRICEINCREDITSOUTPUT) and mention the
map key expected is "gpt5mini".
---
Nitpick comments:
In
`@backend/data/src/main/kotlin/io/tolgee/configuration/tolgee/machineTranslation/LlmProperties.kt`:
- Around line 174-179: LlmProviderDefaults currently hard-codes the default
provider type to LlmProviderType.OPENAI; change it to reuse the shared sentinel
LlmProvider.TYPE_DEFAULT so it stays consistent with mergeProviderWithDefaults.
Update the type property in class LlmProviderDefaults (and any constructor
defaults there) to use LlmProvider.TYPE_DEFAULT instead of
LlmProviderType.OPENAI, ensuring mergeProviderWithDefaults sees the same
sentinel value.
ℹ️ Review info
⚙️ Run configuration
Configuration used: Path: .coderabbit.yaml
Review profile: CHILL
Plan: Pro
Run ID: e0e865cf-ffcf-4ed8-b28c-367d3209092e
📒 Files selected for processing (4)
backend/data/src/main/kotlin/io/tolgee/configuration/tolgee/machineTranslation/LlmProperties.ktbackend/data/src/main/kotlin/io/tolgee/service/LlmPropertiesService.ktwebapp/src/ee/llm/OrganizationLLMProviders/LlmProviderPricingDialog.tsxwebapp/src/ee/llm/OrganizationLLMProviders/LlmProviderPricingInfo.tsx
🚧 Files skipped from review as they are similar to previous changes (2)
- webapp/src/ee/llm/OrganizationLLMProviders/LlmProviderPricingInfo.tsx
- webapp/src/ee/llm/OrganizationLLMProviders/LlmProviderPricingDialog.tsx
| or using environment variables (in a ConfigMap): | ||
|
|
||
| ``` | ||
| TOLGEE_LLM_PROVIDER_DEFAULTS_GPT_5_MINI_TYPE=OPENAI | ||
| TOLGEE_LLM_PROVIDER_DEFAULTS_GPT_5_MINI_MODEL=gpt-5-mini | ||
| TOLGEE_LLM_PROVIDER_DEFAULTS_GPT_5_MINI_TOKEN_PRICE_IN_CREDITS_INPUT=2.0 | ||
| TOLGEE_LLM_PROVIDER_DEFAULTS_GPT_5_MINI_TOKEN_PRICE_IN_CREDITS_OUTPUT=1.5 | ||
| ``` |
There was a problem hiding this comment.
🧩 Analysis chain
🌐 Web query:
Spring Boot official documentation: how are environment variable names derived from kebab-case @ConfigurationProperties names, and how are Map keys bound from environment variables?
💡 Result:
Deriving environment-variable names from canonical (kebab-case) property names
Spring Boot recommends using the canonical form for property names (lowercase kebab-case, with . for nesting). To convert a canonical property name to an environment variable name, Spring Boot documents these rules: replace . with _, remove -, then uppercase. For example, spring.main.log-startup-info becomes SPRING_MAIN_LOGSTARTUPINFO. [1]
Binding Map keys from environment variables
When binding from environment variables, Spring Boot lowercases the environment variable name before binding. This usually doesn’t matter, except for Map bindings: Map keys produced from env vars are always lowercase. Example: if you have @ConfigurationProperties("my.props") with a Map<String,String> values, then setting MY_PROPS_VALUES_KEY=value results in values containing {"key"="value"} (and if MY_PROPS_VALUES_KEY=VALUE, the key is still "key" but the value is "VALUE"). [1]
Source: Spring Boot Reference Guide → Externalized Configuration → “Binding From Environment Variables” and “Binding Maps From Environment Variables”. [1]
🏁 Script executed:
cat -n backend/data/src/main/kotlin/io/tolgee/configuration/tolgee/machineTranslation/LlmProperties.kt | head -100Repository: tolgee/tolgee-platform
Length of output: 4100
🏁 Script executed:
cat -n backend/data/src/main/kotlin/io/tolgee/configuration/tolgee/machineTranslation/LlmProperties.kt | sed -n '100,180p'Repository: tolgee/tolgee-platform
Length of output: 3757
🏁 Script executed:
cat -n backend/data/src/main/kotlin/io/tolgee/configuration/tolgee/machineTranslation/LlmProperties.kt | sed -n '174,200p'Repository: tolgee/tolgee-platform
Length of output: 1531
🏁 Script executed:
rg -A 5 "TOLGEE_LLM_PROVIDER_DEFAULTS" --type kt --type javaRepository: tolgee/tolgee-platform
Length of output: 92
🏁 Script executed:
rg -A 5 "TOLGEE_LLM_PROVIDER_DEFAULTS"Repository: tolgee/tolgee-platform
Length of output: 1294
🏁 Script executed:
# Let me verify the Spring Boot relaxed binding rules by checking any tests or integration examples
fd -e properties -e yml -e yaml | xargs grep -l "provider-defaults" 2>/dev/null | head -5Repository: tolgee/tolgee-platform
Length of output: 48
Fix the provider-defaults env-var example.
Spring Boot derives env-var names by replacing . with _, removing - entirely, and uppercasing. For the canonical property tolgee.llm.provider-defaults.gpt-5-mini.type, the correct env-var is TOLGEE_LLM_PROVIDERDEFAULTSGPT5MINITYPE (no underscores between parts). Additionally, Map keys bound from env vars are always lowercased, so the map key would be gpt5mini, not gpt-5-mini. The documented form TOLGEE_LLM_PROVIDER_DEFAULTS_GPT_5_MINI_TYPE does not follow these rules and will fail to bind to the intended map entry.
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In
`@backend/data/src/main/kotlin/io/tolgee/configuration/tolgee/machineTranslation/LlmProperties.kt`
around lines 71 - 78, Update the env-var examples in LlmProperties so they
follow Spring Boot's binding rules: use the canonical property
tolgee.llm.provider-defaults.gpt-5-mini.type and its env-var form
TOLGEE_LLM_PROVIDERDEFAULTSGPT5MINITYPE (no dashes/underscores between parts)
and note the map key will be lowercased (gpt5mini). Replace the incorrect
examples (e.g. TOLGEE_LLM_PROVIDER_DEFAULTS_GPT_5_MINI_TYPE) with the correct
concatenated uppercase names for type/model and token price keys (e.g.
TOLGEE_LLM_PROVIDERDEFAULTSGPT5MINITYPE,
TOLGEE_LLM_PROVIDERDEFAULTSGPT5MINIMODEL,
TOLGEE_LLM_PROVIDERDEFAULTSGPT5MINITOKENPRICEINCREDITSINPUT,
TOLGEE_LLM_PROVIDERDEFAULTSGPT5MINITOKENPRICEINCREDITSOUTPUT) and mention the
map key expected is "gpt5mini".
Enable separating non-secret LLM provider config (model, prices, type) from secrets (API keys) in Kubernetes deployments via a new `provider-defaults` map that merges with the existing `providers` list.
Add a pricing information dialog for server-configured LLM providers. Organization owners can now see token pricing in credits and estimated costs for translating 1,000 strings. - Extend LlmProviderSimpleModel with tokenPriceInCreditsInput/Output - Create LlmProviderPricingDialog with token prices table, cost estimates (with/without screenshots), and info notes - Create LlmProviderPricingInfo as self-contained icon + dialog - Only show pricing info when billing is enabled - Fetch subscription data to convert credits to USD - Add E2E test verifying pricing info is hidden without billing
Add token pricing description and estimate description labels. Update generated API schemas and data-cy types for pricing dialog.
- Split LlmProviderPricingDialog into smaller components - Use data-cy from tab items instead of hardcoded selector - Use llm-providers-server data-cy in e2e tests - Clarify "Tokens" column as "Tokens (per string)" - Fix BigMeta instruction wording in CLAUDE.md
…t TYPE_DEFAULT constant Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
360a649 to
48a5e82
Compare
# [3.167.0](v3.166.3...v3.167.0) (2026-03-12) ### Features * map-based LLM provider config with pricing dialog ([#3492](#3492)) ([da858c0](da858c0))
Summary
Test plan
Summary by CodeRabbit
New Features
Tests
Documentation